package jmpo.cls;

/**
 * Enum - это специальный класс в Java, который имеет ограниченное количество 
 * возможных значений, объектов-экземпляров.
 */
enum Seasons {
    SUMMER, // каждый описанный вариант - это один из возможных объектов класса
    FALL,   // их принято называть заглавными буквами, но допустим любой идентификатор
    WINTER,
    SPRING
}

/**
 * enum содержит специальные методы для удобства работы
 *     final String name() - строку с задекларированным именем константы
 *     string toString() - повторяет name(), но может быть перегружен
 *     final int ordinal() - порядковый номер элемента от первого по описанию enum,
 *          начинается с 0 и увеличивается на 1
 *     static <enum_type> valueOf(String name) - возвращает элемент перечисления по
 *          строке с именем константы
 *          Выбрасывает IllegalArgumentException
 *     static <enm_type_array> values() - возвращает массив ссылок на все элменты перечисления
 */
/**
 * Хотя в Java нельзя поменять ordinal значения, можно для элементов enum задать 
 * внутренние данные и конструктор. Тогда те значения, с которыми инициализиреутся 
 * элемент enum нужно передать в описание константы.
 */
enum Planets {
    Mercury(1), Venus(2), Earth(3), Mars(4), Jupiter(5), Saturn(6), Uranus(7), Neptune(8);

    final public int getOrd() {
        return this.ord;
    }

    /**
     * конструктор должен быть private
     */
    private Planets(int orderFromTheSun) {
        this.ord = orderFromTheSun;
    }

    private int ord;
}

/**
 * Пользовательский enum по умолчанию наследует классу java.lang.Enum,
 * потому он не может наследовать другим классам, но может реализовывать
 * интерфейсы.
 */
interface Greeting {
    String greetString();
}

enum ImplementingEnum implements Greeting {
    English("Hello"), Russian("Привет"), Spanish("Hola");

    @Override
    public String greetString() {
        return this.greeting;
    }

    private ImplementingEnum(String greeting) {
        this.greeting = greeting;
    }

    private String greeting;
}

/**
 * По сути enum работает через наследование.
 * Потому позволяет задавать абстрактные методы и реализующие их классы.
 * Сам класс при этом не нужно обозначать abstract, так как на самом деле он уже final.
 * Если в enum есть abstract метод, то его обязаны реализовать все варианты этого enum.
 * Если в вариантах есть методы, не важно собственные или унаследованные, то 
 * enum варианты компилируются в отдельные классы. 
 * Инстансы их классов (getClass()) не равны. 
 * Но инстансы getDeclaringClass() одинаковы - это класс самого enum.
 */
enum AbstractEnum {
    RUSSIAN {
        @Override
        public String greetString() {
            return "Привет";
        }
    },

    ENGLISH {
        @Override
        public String greetString() {
            return "Hello";
        } 
    },
    Spanish {
        @Override
        public String greetString() {
            return "Hola";
        }
    };

    public abstract String greetString();
}

public class EnumClass {
    private static boolean simpleEnumTest() {
        var season = Seasons.SPRING; // season - ссылка на один из объектов enum
        var season2 = Seasons.SPRING;
        return season == season2; // так как объект только один, то ссылки на него всегда равны
    }

    private static boolean nameMethodTest() {
        return Seasons.SPRING.name().equals("SPRING");
    }

    private static boolean toStringMethodTest() {
        return Seasons.SPRING.name().equals(Seasons.SPRING.toString());
    }

    private static boolean ordinalMethodTest() {
        return 0 == Seasons.SUMMER.ordinal() 
            && 1 == Seasons.FALL.ordinal()
            && 2 == Seasons.WINTER.ordinal()
            && 3 == Seasons.SPRING.ordinal();
    }

    private static boolean valueOfMethodTest() {
        var season = Seasons.valueOf("SPRING");
        return season == Seasons.SPRING;
    }

    private static boolean valuesMethodTest() {
        var seasons = Seasons.values();
        return seasons[0] == Seasons.SUMMER
        && seasons[1] == Seasons.FALL
        && seasons[2] == Seasons.WINTER
        && seasons[3] == Seasons.SPRING;
    }

    private static boolean enumWithConstructor() {
        var planet = Planets.Earth;
        return planet.getOrd() == 3;
    }

    private static void test(boolean expectTrue) {
        if (expectTrue) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }

    private static boolean implementingEnumTest() {
        Greeting g = ImplementingEnum.Russian;
        return g.greetString().equals("Привет");
    }

    private static boolean abstractEnumTestA() {
        var greeting = AbstractEnum.RUSSIAN;
        return greeting.greetString().equals("Привет");
    }

    private static boolean abstractEnumTestB() {
        return AbstractEnum.RUSSIAN.getClass() != AbstractEnum.ENGLISH.getClass();
    }

    private static boolean abstractEnumTestC() {
        return AbstractEnum.RUSSIAN.getDeclaringClass() == AbstractEnum.ENGLISH.getDeclaringClass(); 
    }
    public static void main(String[] args) {
        test(simpleEnumTest());
        test(nameMethodTest());
        test(toStringMethodTest());
        test(ordinalMethodTest());
        test(valueOfMethodTest());
        test(valuesMethodTest());
        test(enumWithConstructor());
        test(implementingEnumTest());
        test(abstractEnumTestA());
        test(abstractEnumTestB());
        test(abstractEnumTestC());
    }
}